import logging
import datetime

from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from google.appengine.api import users
from google.appengine.ext import db

from GAE.guestbook.models import (\
    WatchItem,
    WebCrawler,
    WebContent
)

from GAE.guestbook.decorators import auth_required
from GAE.lib.gaesessions import get_current_session
from GAE.lib.urlwatch.handler import UrlJob


def _get_date_from_post(date, post_data):
    """
    Retrieves and converts the date from the post data, it either uses the one
    submitted date or the component parts on month, day, year.

    Arguments:
        1. date - a string representing the date's key in the post
        2. post_data - the post data to get the date from.

    Returns:
        datetime.date object of the date in the post.

    Exceptions:
        KeyError if certain keys in POST data do not exist.
        ValueError from datetime.datetime.strptime if conversion fails.
    """
    # If we have the date_month variable in the POST, then we will assume the
    # user hit the application with the javascript feature off. In this case we
    # will build the date to validate from the string components.

    logging.debug("Found '%s' from form with JavaScript disabled." % date)
    # OK, then lets start to set the date as a string for validation.
    # Essentially just changing the component to be a validated date
    # string and turned into a date.
    format_string = '/'.join(["{{0[{0}_month]}}", "{{0[{0}_day]}}",
            "{{0[{0}_year]}}"]).format(date)
    formatted_date = format_string.format(post_data)
    return datetime.datetime.strptime(formatted_date, "%m/%d/%Y").date()


def get_common_data_dict(request):
    '''
    Populate the common data dictionary and return the dictionary.
    '''

    common_data_dict = dict()

    common_data_dict["show_login_info"] = True
    common_data_dict["login_url"] = users.create_login_url(request.path)
    common_data_dict["logout_url"] = users.create_logout_url(request.path)
    common_data_dict["profile_url"] = reverse(profile)
    common_data_dict["manage_url"] = reverse(manage_item)
    common_data_dict["support_url"] = reverse(support)

    # Find out if the user is saved in the database
    query = db.GqlQuery("SELECT * FROM Account where login= :1",
                        users.get_current_user())
    common_data_dict["user"] = \
        get_current_session()["user"] = \
            query.get()

    return common_data_dict


def main(request):
    '''
    Index page for the app
    '''

    data_dictionary = dict()

    # Get the common data dictionary
    data_dictionary.update(get_common_data_dict(request))

    if request.method =="POST":
        return HttpResponseRedirect(reverse(manage_item))

    return render_to_response("main.html", data_dictionary)


@auth_required
def add_edit_item(request):
    '''
    Add or edit the item
    '''

    data_dictionary = dict()
    
    # Get the common data dictionary
    data_dictionary.update(get_common_data_dict(request))

    # Get the session
    session = get_current_session()

    # Clear the validation error dict
    session["validation_errors"] = dict()

    data_dictionary["submit_type"] = "Add"

    # Provide the today's date for the start/end date for add new item
    watch_item_obj = WatchItem(keyword = " ")
    watch_item_obj.start_date = \
        watch_item_obj.end_date = datetime.date.today()

    data_dictionary["watch_item"] = watch_item_obj

    # Handle the edit watch item to populate the fields
    if request.method =="GET" and request.GET.get('watch_item_id'):
        # Get the watch item id from the user
        watch_item_id = request.GET.get('watch_item_id', '').strip()

        # Get the watch item by id
        watch_item_obj = None
        if WatchItem.all().count() > 0:
            watch_item_obj = WatchItem.all().get().\
                get_by_id(int(watch_item_id))

        # Pass the watch item to the template
        if watch_item_obj:
            watch_item_obj.id = watch_item_id
            data_dictionary["submit_type"] = "Update"
            data_dictionary["watch_item"] = \
                request.session["watch_item"] = watch_item_obj
            logging.info(
                "Retrieved the WatchItem to edit: %s", watch_item_obj.name)
            return render_to_response("add_edit_item.html", data_dictionary)

    if request.method =="POST":
        # If the cancel button was clicked. 
        if request.POST.has_key("cancel_submit"):
            return HttpResponseRedirect(reverse(manage_item))

        # Get the user inputs
        keyword = request.POST.get('keyword', '').strip()

        ## START VALIDATIONS
        # Make sure the login is not blank, an empty string or None.
        if not keyword:
            logging.info("Key was empty.")
            session["validation_errors"].update({"keyword_missing": True})

        try:
            start_date = _get_date_from_post('start_date', request.POST)
        except (KeyError, ValueError) as ex:
            logging.error(ex)
            request.session["validation_errors"].update(
                {"start_date_invalid_value": True} )

        try:
            end_date = _get_date_from_post('end_date', request.POST)
        except (KeyError, ValueError) as ex:
            logging.error(ex)
            request.session["validation_errors"].update(
                {"end_date_invalid_value": True} )

        # Check if the start and end date make sense
        if start_date > end_date:
            request.session["validation_errors"].update(
                {"end_date_too_early": True} )

        if start_date < datetime.date.today():
            request.session["validation_errors"].update(
                {"start_date_too_early": True} )

        # If this is the edit watch item, pre-populate the fields
        if request.POST.has_key("edit_watch_item_submit"):
            watch_item_to_edit = \
                data_dictionary["watch_item"] = \
                request.session.get("watch_item")
            data_dictionary["submit_type"] = "Update"

        # Check if Validation error exists.
        if session["validation_errors"]:

            data_dictionary.update(session["validation_errors"])
            return render_to_response("add_edit_item.html", data_dictionary)

        ## END VALIDATIONS

        # Add or edit the watch item
        try:
            # Get the account for the current user
            current_user = request.session.get("user")
            
            # Modify the existing watch item
            if request.POST.has_key("edit_watch_item_submit"):
                logging.info("Entering edit watch item")

                # Pass the watch item to the template
                if watch_item_to_edit:
                    watch_item_to_edit.start_date = start_date
                    watch_item_to_edit.end_date = end_date
                    watch_item_to_edit.keyword = keyword
                    watch_item_to_edit.put()

                return HttpResponseRedirect(reverse(manage_item))

            # Add the new watch item
            elif request.POST.has_key("add_watch_item_submit"):
                logging.info("Entering add watch item")
                watch_item_obj = WatchItem(keyword = keyword)
                watch_item_obj.account = current_user
                watch_item_obj.start_date = start_date
                watch_item_obj.end_date = end_date
                watch_item_obj.put()

                return HttpResponseRedirect(reverse(manage_item))

            else:
                raise

        except Exception as ex:
            logging.error("Failed to add or edit watch item: %s", ex)
            data_dictionary.update(session["validation_errors"])

            return HttpResponseRedirect(reverse(app_fail))

    return render_to_response("add_edit_item.html", data_dictionary)


def app_fail(request):
    '''
    Destination if the error can't recover from the user input.
    '''

    data_dictionary = dict()

    # Get the common data dictionary
    data_dictionary.update(get_common_data_dict(request))

    return render_to_response("app_fail.html", data_dictionary)


@auth_required
def manage_item(request):
    '''
    Manage item view
    '''

    data_dictionary = dict()

    # Get the common data dictionary
    data_dictionary.update(get_common_data_dict(request))

    # Get the session
    session = get_current_session()

    # Clear the validation error dict
    session["validation_errors"] = dict()

    # Find out if the user is saved in the database
    acct_query = db.GqlQuery("SELECT * FROM Account where login= :1",
                        users.get_current_user())

    wi_query = db.GqlQuery("SELECT * FROM WatchItem where account= :1",
                        acct_query.get())

    logging.debug("Watch Items are found for : %s" % users.get_current_user())

    data_dictionary["watch_items"] = wi_query

    if request.method =="POST":
        # Post back to add new watch item
        if request.POST.has_key("add_watch_item_submit"):
            return HttpResponseRedirect(reverse(add_edit_item))

        # Remove the selected watch item
        if request.POST.keys()[0].startswith("delete_"):
            watch_item_id_to_remove = request.POST.keys()[0].split("_")[1]
            watch_item_obj = WatchItem.all().get().\
                get_by_id(int(watch_item_id_to_remove))
            db.delete(watch_item_obj)
            logging.info("Deleted the watch item id: %s " %
                         watch_item_id_to_remove)

    return render_to_response("manage_item.html", data_dictionary)


@auth_required
def profile(request):
    '''
    Profile view
    '''

    data_dictionary = dict()

    # Get the common data dictionary
    data_dictionary.update(get_common_data_dict(request))

    # Get the session
    session = get_current_session()

    # Clear the validation error dict
    session["validation_errors"] = dict()

    if request.method =="POST":
        # Get the user inputs
        email = request.POST.get('email', '').strip()
        account = request.session.get("user")

        # Make sure the email is not blank.
        if not email:
            logging.info("Primary email was empty.")
            session["validation_errors"].update({"email_missing": True})
            data_dictionary.update(session["validation_errors"])
            return render_to_response("profile.html", data_dictionary)

        account.first_name = request.POST.get('first_name', '').strip()
        account.last_name = request.POST.get('last_name', '').strip()
        account.primary_email = request.POST.get('email', '').strip()
        account.put()

        data_dictionary["update_success"] = True


    return render_to_response("profile.html", data_dictionary)


def watch_item(request):
    result = ""
    for item in WatchItem.all():
        result += "%s %s<br/>" % (item.name, item.url)

    return HttpResponse(result)


def fetch_posting():
    crawler = WebCrawler()
    crawler.fetch_all()


def check_html_content(request):
    '''
    Fetch and compare the html content of the bulletin to see
    if new posting is added
    '''

    # Prepare to retrieve the html content
    pkgname = 'urlwatch'
    __version__ = '1.15'
    user_agent = '%s/%s (+http://thp.io/2008/urlwatch/info.html)' % \
        (pkgname, __version__)
    headers = {'User-agent': user_agent,}
    filter_func = lambda x, y: y

    uj = UrlJob("http://illinoisksa.org/xe/fleamarket")
    new_data = uj.retrieve(filter_func=filter_func, headers=headers)

    # If the web content is not empty, check if we need to fetch.
    if WebContent.all().count() > 0:
        q = WebContent.all().get()
        logging.debug("Html Content retrieve: %s" % q.html_content)

        # If the old and new data are same do nothing.
        if len(q.html_content) == len(new_data):
            logging.debug("No change was made to the html content.")
            logging.debug("Therefore, no need to fetch the content.")
            
            return HttpResponse("No change was made to the html content.")
    else:
        q = WebContent()
        logging.debug("Creating a first html content.")

    # Update the cache html content
    q.html_content= new_data
    q.put()

    # Now fetch the new postings
    fetch_posting()
    
    # Update the posting data
    return HttpResponse("Fetching the posting is done.")



def support(request):
    '''
    Support page for the app
    '''

    data_dictionary = dict()

    # Get the common data dictionary
    data_dictionary.update(get_common_data_dict(request))

    return render_to_response("support.html", data_dictionary)

